38. SpringMVC3 - Json 的处理, 文件上传下载, 拦截器Demo

处理 JSON

http 400:客户端发送的请求服务器端无法处理
http 405:请求方法不匹配
http 406:服务器端返回的数据客户端无法处理

开启 MVC 驱动

在 SpringMVC 的配置文件中,需要开启 MVC 驱动。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">


<context:component-scan base-package="com.auguigu"></context:component-scan>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

<!-- 开启 MVC 驱动 -->
<!-- 开启 SpringMVC 自动将 Java 对象转换为 Json 的能力。(使用的是 jackson,需要导入 jar 包) -->
<mvc:annotation-driven/>
</beans>

导入 jar 包

SpringMVC 有自动将 Java 对象转换为 Json 的功能,里面使用的是 jackson,所以需要我们导入相应 jar 包。

1
2
3
jackson-annotations-2.1.5.jar
jackson-core-2.1.5.jar
jackson-databind-2.1.5.jar

加上 ResponseBody

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
package com.auguigu.test;

import java.util.Collection;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

import com.auguigu.bean.Employee;
import com.auguigu.dao.EmployeeDao;

@Controller
public class TestJsonController {
@Autowired
private EmployeeDao employeeDao;

@RequestMapping("/testJson")
@ResponseBody
public String testJson() {
return "success";
}

@RequestMapping("/testJson2")
@ResponseBody
public Collection<Employee> testJson2() {
Collection<Employee> allEmployee = employeeDao.getAll();
return allEmployee;
}

@RequestMapping(value = "/testJson3", method = RequestMethod.POST)
@ResponseBody
public Collection<Employee> testJson3() {
Collection<Employee> allEmployee = employeeDao.getAll();
return allEmployee;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<script type="text/javascript" src="${ pageContext.servletContext.contextPath }/js/jquery-1.8.2.min.js"></script>
<script type="text/javascript">
$(function(){
$("#btn").click(function(){
$.ajax({
url:"testJson3",
type:"POST",
data: {},
dataType:"json",
success: function(msg){
alert(msg)
}
})
})
})
</script>
<body>
<a href="testJson"> 测试 JSON </a> <br/>
<a href="testJson2"> 测试 Java Object JSON </a> <br/>
<input type="button" id="btn" value="测试 Ajax "/>
</body>
</html>

文件上传/下载

文件上传下载不要求强制记忆,只需会使用就行。

下载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
package com.auguigu.test;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.servlet.http.HttpSession;

import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
public class TestUploadAndDownController {

@RequestMapping(value = "/down")
public ResponseEntity<byte []> down(HttpSession session) throws IOException {
String fileName = "1.jpg";

// 获取下载文件的路径
String path = session.getServletContext().getRealPath("img");
String finalPath = path + File.separator + fileName;

// 读取文件
InputStream inputStream = new FileInputStream(finalPath);

// available 获取输入流读取的文件的最大字节
byte[] body = new byte[inputStream.available()]; // 创建一个字节数组
inputStream.read(body); // 将输入流放入字节数组中

// 设置响应头
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add("Content-Disposition", "attachment;filename=" + fileName);

// 设置响应状态
HttpStatus statusCode = HttpStatus.OK;

// 返回文件流信息
ResponseEntity<byte[]> responseEntity = new ResponseEntity<byte []>(body, httpHeaders, statusCode);

return responseEntity;
}
}

上传

SpringMVC 中已经为我们提供了可以上转文件的 MultipartFile,它将客户端上传的 File 文件处理为 MultipartFile。id 的值必须为 multipartResolver。SpringMVC 中的配置文件如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">


<context:component-scan base-package="com.auguigu"></context:component-scan>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

<!-- 处理静态资源 -->
<mvc:default-servlet-handler/>
<!-- 开启 MVC 驱动 -->
<!-- 开启 SpringMVC 自动将 Java 对象转换为 Json 的能力。(使用的是 jackson,需要导入 jar 包) -->
<mvc:annotation-driven/>

<!-- 处理文件,将客户端上传的 File 文件处理为 MultipartFile。
id 的值必须为 multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置文件解析编码,需要和页面的配置保持一致 -->
<property name="defaultEncoding" value="UTF-8"></property>
<!-- 设置上传文件的最大大小,不支持表达式 -->
<property name="maxUploadSize" value="99999999"></property>
</bean>
</beans>

导入 apache commons jar 包

1
2
commons-fileupload-1.3.1.jar
commons-io-2.4.jar

保存文件有两种方式,一种是获取输入流,然后创建输出流。另一种是使用 MultipartFile 直接封装好的方法,直接保存文件。两种方式示例如下:

方式1: 通过创建文件输入/输出流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@ResponseBody
@RequestMapping(value = "/upload1", method = {RequestMethod.POST})
// SpringMVC 将客户端接口到的文件对象转换为 MultipartFile 对象
public String upload1(String desc, MultipartFile uploadFile, HttpSession session) throws IOException{
// 获取上传的文件的名称
String originalFilename = uploadFile.getOriginalFilename();

// 设置文件上传路径
String path = session.getServletContext().getRealPath("photo") + File.separator + originalFilename;

// 获取输入流
InputStream inputStream = uploadFile.getInputStream();

// 获取输出流
File file = new File(path);
OutputStream outputStream = new FileOutputStream(file);

// 边读边写文件
int i =0;
byte[] b = new byte[1024];
while ((i = inputStream.read(b))!=-1) {
outputStream.write(b, 0, i);
}

// 关闭数据流
outputStream.close();
inputStream.close();
return "success";

方式1: 通过MultipartFile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@ResponseBody
@RequestMapping(value = "/upload2", method = {RequestMethod.POST})
// SpringMVC 将客户端接口到的文件对象转换为 MultipartFile 对象
public String upload2(String desc, MultipartFile uploadFile, HttpSession session) throws IOException{
// 获取上传的文件的名称
String originalFilename = uploadFile.getOriginalFilename();

// 重命令
String finalFileName = UUID.randomUUID().toString().replace("-", "") + originalFilename.substring(originalFilename.lastIndexOf("."));

// 设置文件上传路径
String path = session.getServletContext().getRealPath("photo") + File.separator + finalFileName;

// 使用 uploadFile 的上传
File file = new File(path);
uploadFile.transferTo(file);
return "success";
}

拦截器

概念

Spring MVC也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器可以实现HandlerInterceptor接口,也可以继承 HandlerInterceptorAdapter 适配器类

注意⚠️:过滤器是请求发送后,先经过过滤器,然后再将请求交给 Servlet 处理,而拦截器是在经过 Servlet 处理后,业务处理器处理请求的时候进行处理。即: 客户端 –> Filter –> DispatcherServlet –> 拦截器 –> Handler(Controller)

preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回 true;如果程序员决定不需要再调用其他的组件去处理请求,则返回 false。即返回 true 表示放行,返回 false 表示进行拦截。

postHandle():这个方法在业务处理器处理完请求后,是 DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求 request 进行处理。只有正确返回才会调用,如果 Handler 中出错是不会被调用的。

afterCompletion():这个方法在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。不管 Handler 中是否出现错误都会执行。

单个拦截器示例

定义一个 FirstInterceptor 类,并实现 HandlerInterceptor 接口。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package com.auguigu.interceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;

@Component
public class FirstInterceptor implements HandlerInterceptor{

@Override
public void afterCompletion(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, Exception arg3)throws Exception {
System.out.println("afterCompletion, 相当于在 finally 里面,无论失败成功都会执行");
}

@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2, ModelAndView arg3)throws Exception {
System.out.println("postHandle, 正确的返回之后之后才会执行,位置相当于在 ModelAndView 之后");
}

@Override
public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1, Object arg2) throws Exception {
System.out.println("preHandle,Handler 开始处理请求之前");

//返回 true 表示放行,返回 false 表示进行拦截。
return true;
}
}

在 SpringMVC 配置文件中应用该拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">


<context:component-scan base-package="com.auguigu"></context:component-scan>
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/view/"></property>
<property name="suffix" value=".jsp"></property>
</bean>

<!-- 处理静态资源 -->
<mvc:default-servlet-handler/>
<!-- 开启 MVC 驱动 -->
<!-- 开启 SpringMVC 自动将 Java 对象转换为 Json 的能力。(使用的是 jackson,需要导入 jar 包) -->
<mvc:annotation-driven/>

<!-- 处理文件,将客户端上传的 File 文件处理为 MultipartFile。
id 的值必须为 multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置文件解析编码,需要和页面的配置保持一致 -->
<property name="defaultEncoding" value="UTF-8"></property>
<!-- 设置上传文件的最大大小,不支持表达式 -->
<property name="maxUploadSize" value="99999999"></property>
</bean>

<!-- 配置拦截器 -->
<mvc:interceptors>
<!-- 第一种方式 -->
<!-- <bean class="com.auguigu.interceptor.FirstInterceptor"></bean> -->

<!-- 第二种方式,在 FirstInterceptor 类上加上 @Component 注解,然后使用 ref 引入-->
<ref bean="firstInterceptor"/>
</mvc:interceptors>
</beans>

新建 Controller 进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.auguigu.test;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class TestInterceptorController {

@ResponseBody
@RequestMapping(value = "/testInterceptor")
public String testInterceptor() {
return "success";
}
}

输出内容

1
2
3
preHandle,Handler 开始处理请求之前
postHandle, 正确的返回之后之后才会执行,位置相当于在 ModelAndView 之后
afterCompletion, 相当于在 finally 里面,无论失败成功都会执行

自定义拦截方式

我可以可以针对某些请求进行拦截。并不是上面例子中的拦截所有。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 <!-- 配置拦截器 -->
<mvc:interceptors>
<!-- 第一种方式 -->
<!-- <bean class="com.auguigu.interceptor.FirstInterceptor"></bean> -->

<!-- 第二种方式,在 FirstInterceptor 类上加上 @Component 注解,然后使用 ref 引入-->
<ref bean="firstInterceptor"/>

<!-- 设置自定义拦截方式 -->
<mvc:interceptor>
<bean></bean> <!-- 拦截器 bean -->
<mvc:mapping path=""/> <!-- 自定义拦截路径 -->
<mvc:exclude-mapping path=""/> <!-- 自定义不拦截路径 -->
</mvc:interceptor>
</mvc:interceptors>

例子

1
2
3
4
5
6
7
8
9
10
11
12
<mvc:interceptors>
<!-- 声明自定义拦截器 -->
<bean id="firstHandlerInterceptor"
class="com.atguigu.springmvc.interceptors.FirstHandlerInterceptor"></bean>
<!-- 配置拦截器引用 -->
<mvc:interceptor>                        
<mvc:mapping path="/empList"/>
<!-- <mvc:exclude-mapping path="/empList"/> -->
<bean id="secondHandlerInterceptor"
class="com.atguigu.springmvc.interceptors.SecondHandlerInterceptor"></bean>
</mvc:interceptor>
</mvc:interceptors>

多个拦截器执行顺序

当有多个拦截器时,如果多个拦截器的 preHandle 方法返回的都是 true 时,preHandle 的执行顺序是按照拦截器配置顺序正向执行的。postHandle 和 afterCompletion 是按照拦截器拦截器配置顺序反向执行的。

当有多个拦截器时,如果多个拦截器的 preHandle 方法返回都是 false,那么只有第一个拦截器的 preHandle 会被执行。

当有两个(多个)拦截器时,如果第一个拦截器的 preHandle 方法返回的是 true,第二个拦截器的 preHandle 方法返回 false。那么两个(多个)拦截器的 preHandle 方法都会执行,但是 postHandle 都不会执行。afterCompletion 只有第一个(返回 false 的拦截器之前的所有拦截器)拦截器执行。

当有多个拦截器时,如果第一个拦截器的 preHandle 方法返回的是 false,第二个拦截器的 preHandle 方法返回 true。这时只有第一个拦截器的 preHandle 会执行。

代码地址